feat(compliance): the asymmetric lower-bound liquidity gate (#696, pa… - #711
Merged
Conversation
…rt 2 of 2) Part 1 recorded WHICH feed a series came from. This is what reads it. THE GATE. Venue volume is a LOWER BOUND on consolidated volume, never an upper one, so the two directions are not equally informative and the gate treats them differently: * at or above the floor, on ANY feed -> CONCLUSIVE PASS. A name that traded that much on one venue alone necessarily traded at least that much in total. A gate that refused here would ban MSFT and AAPL for thinness they do not have, and would turn a data-vendor pricing tier into a prerequisite for running the engine at all. * below the floor, on a partial feed -> NOT A VERDICT. Equally consistent with a thin asset and with a liquid one that barely trades HERE. Refused as `liquidity_unmeasured`, naming the feed and both dollar figures, and saying consolidated volume is UNMEASURED rather than low. A test asserts the line never contains "illiquid" or "too thin": the refusal must not assert the half it cannot see. * below the floor, on a consolidated feed -> unchanged. The crypto path is untouched, because Coinbase's own volume IS the scale the floor was calibrated against. * below the floor, scope UNRECORDED -> unchanged verdict, annotated. Every series cached before v17 is unrecorded, and treating unrecorded as partial would refuse the entire existing universe over metadata nobody wrote down. It self-heals as series are re-fetched. `market_facts` reads ONE_DAY provenance specifically -- that is the granularity the statistic medians, so an hourly series on another feed says nothing about it. `doctor` gains `data.feed_scope`, WARN and never FAIL: a free single-venue tier is a legitimate configuration, and the bound admits its liquid names honestly. What is not legitimate is not knowing, and the screen only speaks for a candidate somebody is actively screening while `slippage_for_quote_volume` prices off the same number every cycle in silence. Partial and unrecorded series are reported SEPARATELY: one should be re-fetched under a different feed, the other may already be consolidated. The cost-fidelity record is amended rather than rewritten (`docs/experiments` is append-only). The amendment retracts two claims that were asserted rather than measured -- "roughly 2% ... approximately IEX's share" (IEX publishes ~3.8% for Q2 2026, so the figure was simply wrong) and the "~50x" understatement -- notes that no number in the document depended on either, and records that what shipped is better than what that section proposed. Tests (written first, red before green): 27 across three files. tests/compliance/test_liquidity_lower_bound.py -- 11 tests/compliance/test_market_facts_carries_feed_scope.py -- 6 tests/commands/test_doctor_feed_scope.py -- 8 (+2 existing) Two EXISTING reachability guards -- one in tests/compliance, one in tests/test_proposer -- correctly failed: they assert every `DATA_DERIVED_FAILURES` tag really appears in `screen_asset` output, and `liquidity` / `liquidity_unmeasured` are mutually exclusive by construction, so no single set of facts emits both. Both now screen the partial-feed arm too, rather than the tag being dropped as unreachable. Mutation-verified, 7 mutants. One SURVIVED first time: gutting the fix line's explanatory half left `keel fetch` intact, and the assertion only looked for "keel". Re-fetching under the SAME feed changes nothing, so the pin now requires the word "consolidated" -- an operator who re-fetches identically gets the same warning and concludes the report is broken. Closes #696 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL
Three findings from the review of #711. The first silenced the report in the exact configuration the feature exists for. 1. `feed_scope_findings` returned early on the partial group, so unrecorded series were dropped whenever any partial series existed -- which is the ORDINARY state of this deployment: equities on a single-venue feed alongside crypto series cached before provenance existed. Verified: `{MSFT-USD: (alpaca:iex,), ETH-USD: ()}` reported only MSFT. Every test in the new file exercised one category alone, which is exactly how that survived a suite written test-first. Both groups now report in ONE finding -- one rather than two so `data.feed_scope` stays a single name for the name-coverage pin -- with a headline counting both and a fix naming both remedies, which are genuinely different actions: a bounded series needs re-fetching under a CONSOLIDATED feed, an unrecorded one needs re-fetching under any feed just to stamp it. 2. The doctor hook enumerated `products x granularities` unconditionally, so a never-fetched series read as "predates feed provenance". On the seeded test database that produced nine phantom warnings. A series with no bars has no provenance because it has no BARS, and `data.missing` already reports it. The hook now filters on `n_candles > 0`, reusing the `assess_products` rows the health report was already computing rather than sweeping twice. 3. `f"{Decimal('5E+5'):,}"` is `5E+5`, not `500,000` -- Decimal keeps whatever exponent its arithmetic produced, and `median = volume * close` can land there. The refusal line is what an operator reads to decide whether a series is worth re-fetching, and `5E+5` against `1,000,000` is not a comparison anyone should be asked to make. Both figures now go through a `_dollars` helper that normalises the exponent away. Tests written first, red before green: 4 new (3 on the coexisting groups, 1 on the exponent form) plus `test_feed_scope_ignores_series_with_no_bars`, which failed with all nine phantom series before the fix. Mutation-verified, 3 mutants, all killed: the early return restored; the bar-count filter widened to `>= 0`; the dollar helper's `quantize` removed. Refs #696 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…rt 2 of 2)
Part 1 recorded WHICH feed a series came from. This is what reads it.
THE GATE. Venue volume is a LOWER BOUND on consolidated volume, never an upper one, so the two directions are not equally informative and the gate treats them differently:
liquidity_unmeasured, naming the feed and both dollar figures, and saying consolidated volume is UNMEASURED rather than low. A test asserts the line never contains "illiquid" or "too thin": the refusal must not assert the half it cannot see.market_factsreads ONE_DAY provenance specifically -- that is the granularity the statistic medians, so an hourly series on another feed says nothing about it.doctorgainsdata.feed_scope, WARN and never FAIL: a free single-venue tier is a legitimate configuration, and the bound admits its liquid names honestly. What is not legitimate is not knowing, and the screen only speaks for a candidate somebody is actively screening whileslippage_for_quote_volumeprices off the same number every cycle in silence. Partial and unrecorded series are reported SEPARATELY: one should be re-fetched under a different feed, the other may already be consolidated.The cost-fidelity record is amended rather than rewritten (
docs/experimentsis append-only). The amendment retracts two claims that were asserted rather than measured -- "roughly 2% ... approximately IEX's share" (IEX publishes ~3.8% for Q2 2026, so the figure was simply wrong) and the "~50x" understatement -- notes that no number in the document depended on either, and records that what shipped is better than what that section proposed.Tests (written first, red before green): 27 across three files.
tests/compliance/test_liquidity_lower_bound.py -- 11
tests/compliance/test_market_facts_carries_feed_scope.py -- 6
tests/commands/test_doctor_feed_scope.py -- 8 (+2 existing)
Two EXISTING reachability guards -- one in tests/compliance, one in tests/test_proposer -- correctly failed: they assert every
DATA_DERIVED_FAILUREStag really appears inscreen_assetoutput, andliquidity/liquidity_unmeasuredare mutually exclusive by construction, so no single set of facts emits both. Both now screen the partial-feed arm too, rather than the tag being dropped as unreachable.Mutation-verified, 7 mutants. One SURVIVED first time: gutting the fix line's explanatory half left
keel fetchintact, and the assertion only looked for "keel". Re-fetching under the SAME feed changes nothing, so the pin now requires the word "consolidated" -- an operator who re-fetches identically gets the same warning and concludes the report is broken.Closes #696
Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL
What & why
Tests-first evidence
Gates (all must pass)
uv run ruff checkcleanuv run mypycleanuv run pytest -qgreenScope check
leave checked only if true, and if so: cite the source and open the discussion
BEFORE review (CONTRIBUTING.md, "Governance: rulings vs. machinery").